/*
 * $Id: JsonRpcOperationInvoker.java 71295 2007-09-26 19:52:43Z jdamick $
 *
 * Copyright 2006- Revolution Health Group.  All rights reserved.
 *
 * This software is the confidential and proprietary information
 * of Revolution Health Group.  (Confidential Information).
 * You shall not disclose such Confidential Information and shall
 * use it only in accordance with the terms of the license 
 * agreement you entered into with Revolution Health Group.
 *
 */
package org.javason.jsonrpc.impl;

import java.beans.PropertyDescriptor;
import java.lang.reflect.Array;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;

import javax.xml.datatype.XMLGregorianCalendar;

import org.apache.commons.beanutils.BeanUtilsBean;
import org.apache.commons.beanutils.Converter;
import org.apache.commons.beanutils.PropertyUtilsBean;
import org.apache.commons.beanutils.converters.AbstractArrayConverter;
import org.apache.commons.beanutils.converters.BigDecimalConverter;
import org.apache.commons.beanutils.converters.BigIntegerConverter;
import org.apache.commons.beanutils.converters.BooleanConverter;
import org.apache.commons.beanutils.converters.DoubleConverter;
import org.apache.commons.beanutils.converters.IntegerConverter;
import org.apache.commons.beanutils.converters.LongConverter;
import org.apache.log4j.Logger;
import net.sf.json.JSONArray;
import net.sf.json.JSONException;
import net.sf.json.JSONObject;

import org.javason.json.JsonToBeanDeserializer;
import org.javason.jsonrpc.JsonRpcMessageContext;
import org.javason.util.ClassUtils;
import org.javason.util.Metrics;

public class JsonRpcOperationInvoker extends JsonToBeanDeserializer {
	private JsonRpcService _service = null;
	private static final Logger LOG = Logger.getLogger(JsonRpcOperationInvoker.class);
	public static final String VERSION = "$Rev: 71295 $";
	
	public JsonRpcOperationInvoker(JsonRpcService service) {
		_service = service;
	}
	
	public static class OperationAndParams {
        public JsonRpcOperation operation;
        public List<Object> invokeParams;
    }

    public OperationAndParams getMatchingOperationAndParams(String methodName, JSONArray params, JsonRpcMessageContext msgCtx) {
        List<JsonRpcOperation> jsonRpcOps = _service.getOperations().get(methodName);
        List<Object> invokeParams = new ArrayList<Object>();
        
        if(jsonRpcOps != null) {        
            for (JsonRpcOperation operation : jsonRpcOps) {
                if (operation.getNumberOfJsonRpcParameters() == params.size()) {
                    invokeParams = getInvocationParameters(operation, params);
                    if (invokeParams.size() == operation.getParameterTypes().size()) {
                        OperationAndParams op = new OperationAndParams();
                        op.invokeParams = invokeParams;
                        op.operation = operation;
                        return op;
                    }
                }
            }
        }
        return null;
    }
    
    public Object invoke(JsonRpcInvocationRequest request) throws IllegalArgumentException, NoSuchMethodException, InvocationTargetException, IllegalAccessException {
    	Object result = null;
    	Metrics invokeTimer = Metrics.beginCollection();
    	try {
    		if (request.hasArrayParams()) {
	    		result = invokeMethod(request.getMethodName(), (JSONArray) request.getParams(), request.getMessageContext());
	    	} else if (request.hasObjectParams()) {
	    		result = invokeMethod(request.getMethodName(), (JSONObject) request.getParams(), request.getMessageContext());
	    	}
    	} finally {
    		invokeTimer.endCollectionAndLogCurrentMethod(String.format("[%s,%s,%s]", request.getId(), request.getMethodName(), request.getParams()));
    	}
    	return result;
    }
    
	public Object invokeMethod(String methodName, JSONArray params, JsonRpcMessageContext msgCtx) 
			throws NoSuchMethodException, IllegalArgumentException, InvocationTargetException, IllegalAccessException {
	
        OperationAndParams op = getMatchingOperationAndParams(methodName, params, msgCtx);
		JsonRpcOperation operationMatch = (op == null) ? null : op.operation;
		List<Object> invokeParams = (op == null) ? null : op.invokeParams;
		
		Object model = null;
		
		if (operationMatch != null) {
			if (operationMatch.isNeedsContext()) {
				invokeParams.add(msgCtx);
			}
			
			Method method = operationMatch.getMethod();
			if (method != null) {
				model = method.invoke(_service.getServiceBean(), invokeParams.toArray(new Object[0]));
			}
		} else {
			throw new NoSuchMethodException();
		}
		
		return model;
	}
	
	public Object invokeMethod(String methodName, JSONObject params, JsonRpcMessageContext msgCtx) 
			throws NoSuchMethodException, IllegalArgumentException, InvocationTargetException, IllegalAccessException {

		List<JsonRpcOperation> jsonRpcOps = _service.getOperations().get(methodName);
		Object result = null;
		if (jsonRpcOps != null && jsonRpcOps.size() > 0) {
			try {
				JSONArray arrayParams = orderParameters(jsonRpcOps.get(0), params);
				result = invokeMethod(methodName, arrayParams, msgCtx);
			} catch (JSONException e) {
				throw new IllegalArgumentException(e);
			}
		} else {
			throw new NoSuchMethodException();
		}
		return result;
	}
	
	private JSONArray orderParameters(JsonRpcOperation operation, JSONObject jsonParams) 
			throws JSONException {
		
		HashMap<String, Object> params = new HashMap<String, Object>();
		
		Iterator keys = jsonParams.keys();
		while (keys.hasNext()) {
			String key = (String)keys.next();
			params.put(key, jsonParams.get(key));
		}
		
		JSONArray orderedParams = new JSONArray();
		JsonRpcDescription description = new JsonRpcDescription(_service);
		List<String> orderedNames = description.getParamNames(operation);
		int index = 0;
		for (String orderedName : orderedNames) {
			orderedParams.add(index, params.get(orderedName));
			index++;
		}
		
		return orderedParams;
	}
	
	private List<Object> getInvocationParameters(JsonRpcOperation operation, JSONArray params) {
		List<Object> invokeParams = new ArrayList<Object>();
		
		try {
			List<Class> paramTypes = operation.getParameterTypes();
			int index = 0;
			for (Class paramType : paramTypes) {
				Object param = getType(paramType, 
						ClassUtils.getArrayElementType(operation.getMethod(), index),
						params, index);
				
				invokeParams.add(param);
				index++;
			}
			
		} catch (JSONException e) {
			e.printStackTrace();
			LOG.error("Caught JSON Exception: ", e);
		} catch (IllegalAccessException e) {
			e.printStackTrace();
			LOG.error("Caught IllegalAccessException: ", e);
		} catch (InvocationTargetException e) {
			e.printStackTrace();
			LOG.error("Caught InvocationTargetException: ", e);
		}
		
		return invokeParams;
	}

	private Object getType(Class paramType, Class parameterizedType, JSONArray jsonArray, int index) 
			throws JSONException, IllegalAccessException, InvocationTargetException {
		
		Object result = null;
		try {
			result = convertType(paramType, jsonArray.opt(index), parameterizedType);
		} catch (InstantiationException e) {
			e.printStackTrace();
			throw new JSONException(e.getMessage());
		}
		return result;
	}
	
}
